package com.bungarus.model;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufHolder;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelId;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.ChannelGroupFuture;
import io.netty.channel.group.ChannelMatcher;
import io.netty.channel.group.ChannelMatchers;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.StringUtil;
import org.apache.log4j.Logger;

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by tang on 2018/11/12.
 */
public class SessionContainer extends AbstractSet<Channel> implements ChannelGroup {
    Logger logger = Logger.getLogger(SessionContainer.class);
    private static final AtomicInteger nextId = new AtomicInteger();
    private final String name;
    private final ConcurrentMap<String, Channel> id2Channels;
    private final ConcurrentMap<String, Session> id2Sessions;
    private final ChannelFutureListener remover;

    public SessionContainer(){
        this("session-container-0x" + Integer.toHexString(nextId.incrementAndGet()));
    }

    public SessionContainer(String name){
        this.id2Channels = PlatformDependent.newConcurrentHashMap();
        this.id2Sessions = PlatformDependent.newConcurrentHashMap();
        this.remover = new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                SessionContainer.this.remove(channelFuture.channel());
            }
        };
        if(null == name){
            throw new NullPointerException("name");
        }
        else {
            this.name = name;
        }
    }

    private static Object safeDuplicate(Object message){
        if(message instanceof ByteBuf){
            return ((ByteBuf)message).retainedDuplicate();
        }
        else {
            return message instanceof ByteBufHolder ? ((ByteBufHolder)message).retainedDuplicate() : ReferenceCountUtil.retain(message);
        }
    }

    public String name() {
        return this.name;
    }

    public Channel find(Session session){
        String id = session.getId();
        Channel c = this.id2Channels.get(id);
        return c;
    }

    public Session findSession(String sessionID) {
        return this.id2Sessions.get(sessionID);
    }

    /**
     * get session by channelid
     * @param id
     * @return
     */
    public Session findSessionByChannelId(ChannelId id) {
        return this.id2Sessions.get(id.asLongText());
    }

    public Channel find(ChannelId id){
        return this.id2Channels.get(id.asLongText());
    }

    public boolean isEmpty() {
        return this.id2Sessions.isEmpty() && this.id2Channels.isEmpty();
    }

    public int size() {
        return this.id2Sessions.size();
    }

    public boolean contains(Object o){
        if( o instanceof Channel){
            return this.id2Channels.containsValue(o);
        }
        else {
            return o instanceof Session && this.id2Sessions.containsValue(o);
        }
    }

    public boolean add(Session session, Channel channel){
        Object o1 = id2Channels.put(channel.id().asLongText(), channel);
        Object o2 = id2Sessions.put(channel.id().asLongText(), session);

        boolean added = (o1 == null) && (o2 == null);
        if(added){
            channel.closeFuture().addListener(this.remover);
        }
        return added;
    }

    public boolean add(Channel channel){
        throw new IllegalArgumentException("not support");
    }

    public boolean remove(Object o){
        Channel c = null;
        String id = null;
        if(o instanceof Session){
            id = ((Session)o).getId();
            this.id2Sessions.remove(id);
            c = this.id2Channels.remove(id);
        }
        else if(o instanceof Channel){
            c = this.id2Channels.remove(((Channel)o).id().asLongText());
            this.id2Sessions.remove(((Channel) o).id().asLongText());
        }

        if(null == c){
            return false;
        }
        else {
            c.closeFuture().removeListener(this.remover);
            return true;
        }
    }

    public void clear(){
        this.id2Sessions.clear();
        this.id2Channels.clear();
    }

    public Iterator<Channel> iterator(){
        throw new IllegalArgumentException("not support");
    }

    public Object[] toArray() {
        throw new IllegalArgumentException("not support");
    }

    public <T> T[] toArray(T[] a){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture close() {
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture disconnect(){
        throw new IllegalArgumentException("not suppport");
    }

    public ChannelGroupFuture deregister(){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture write(Object message) {
        return this.write(message, ChannelMatchers.all());
    }

    public ChannelGroupFuture write(Object message, ChannelMatcher matcher){
        return this.write(message, matcher, false);
    }

    public ChannelGroupFuture write(Object message, ChannelMatcher matcher, boolean voidPromise){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroup flush() {
        return this.flush(ChannelMatchers.all());
    }

    public ChannelGroupFuture flushAndWrite(Object message){
        return this.writeAndFlush(message);
    }

    public ChannelGroupFuture writeAndFlush(Object message){
        return this.writeAndFlush(message, ChannelMatchers.all());
    }

    public ChannelGroupFuture disconnect(ChannelMatcher matcher){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture close(ChannelMatcher matcher) {
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture deregister(ChannelMatcher matcher){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroup flush(ChannelMatcher matcher){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture flushAndWrite(Object message, ChannelMatcher matcher){
        return this.writeAndFlush(message, matcher);
    }

    public ChannelGroupFuture writeAndFlush(Object message, ChannelMatcher matcher){
        return this.writeAndFlush(message, matcher, false);
    }

    public ChannelGroupFuture writeAndFlush(Object message, ChannelMatcher matcher, boolean voidPrimise){
        throw new IllegalArgumentException("not support");
    }

    public ChannelGroupFuture newCloseFuture(){
        return this.newCloseFuture(ChannelMatchers.all());
    }

    public ChannelGroupFuture newCloseFuture(ChannelMatcher matcher){
        throw new IllegalArgumentException("not support");
    }

    public int hashCode(){
        return System.identityHashCode(this);
    }

    public boolean equals(Object o){
        return this == o;
    }

    public int compareTo(ChannelGroup o){
        int v = this.name().compareTo(o.name());
        return v != 0 ? v : System.identityHashCode(this) - System.identityHashCode(o);

    }

    public String toString() {
        return StringUtil.simpleClassName(this) + "(name: " + this.name() + ", size: " + this.size() + ")";
    }
}
